﻿using System;
using System.Collections.Generic;
using System.Linq;
using LightPersist.Exceptions;
using LightPersist.Transactions;

namespace LightPersist
{
	/// <summary>
	/// Provides methods for Directed Acyclic Graph checking
	/// </summary>
	internal static class DagIntegrityChecker
	{
		private static IEnumerable<TheAction> GetAllActions(Transaction transaction)
		{
			if (transaction.SkipDuringProcess)
			{
				yield break;
			}

			foreach (var actionEntry in transaction.Actions)
			{
				foreach (var action in GetAllActions(actionEntry))
				{
					yield return action;
				}
			}
		}

		private static IEnumerable<TheAction> GetAllActions(ActionEntry actionEntry)
		{
			if (actionEntry.IsTransaction)
			{
				if (actionEntry.AsTransaction.SkipDuringProcess)
				{
					yield break;
				}

				foreach (var action1 in actionEntry.AsTransaction.Actions)
				{
					foreach (var action2 in GetAllActions(action1))
					{
						yield return action2;
					}
				}
			}
			else
			{
				yield return actionEntry.AsAction;
			}
		}

		public static void CheckIntegrity(Transaction transaction)
		{
			try
			{
				CheckIntegrity(GetAllActions(transaction).Select(x => x.TargetModel).ToArray());
			}
			catch
			{
				transaction.State = TransactionState.Error;
				throw;
			}
		}

		public static void CheckIntegrity(params ModelBase[] modelsToCheck)
		{
			foreach (var modelToCheck in modelsToCheck)
			{
				CheckIntegrity(modelToCheck);
			}
		}

		public static void CheckIntegrity(ModelBase modelToCheck)
		{
			try
			{
				var ctx = new DagCheckingContext();
				CheckIntegrity(modelToCheck, ctx);
				ctx.EnshureStackIsEmpty();
			}
			catch (ArgumentException ex)
			{
				throw new DagException("Cycle in DAG", ex);
			}
		}

		private static void CheckIntegrity(ModelBase model, DagCheckingContext ctx)
		{
			ctx.Push(model.ID);
			foreach (var modelParent in model.StrongBackReferences.Select(x => model.ObjectState.LightPersistContext.GetObjectByID(x.ID)))
			{
				CheckIntegrity(modelParent, ctx);
			}
			ctx.Pop();
		}
	}
}
